# coding=utf-8
import os

from selenium import webdriver
from selenium.common.exceptions import *
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from Resource.config import Config
import datetime
import allure


class BasePage(Config):
    # base_url = ""

    current_path = os.path.dirname(os.path.abspath(__file__))
    capture_path = os.path.abspath(os.path.join(current_path, "../Report/capture"))

    def __init__(self, driver: WebDriver = None, base_url=""):
        super(BasePage, self).__init__()
        self.base_url = base_url
        # 没传driver变量，默认为None，说明是新开的浏览器，否则复用跳转时传入的driver
        if driver is None:
            options = Options()
            # 脚本调试，通过此端口打开浏览器后复用此浏览器，
            # 启动命令windows：chrome --remote-debugging-port=9222
            # mac：Google\ Chrome --remote-debugging-port=9222
            # options.debugger_address = "localhost:9222"
            # 开启无头模式
            # options.add_argument('--headless')
            remote_url = self.conf('remote_url')
            enable_headless = self.using_headless()
            # 是否使用无头模式
            if enable_headless:
                options.add_argument('--headless')
            # 是否使用远程执行
            if remote_url:
                self.driver = webdriver.Remote(command_executor=remote_url, options=options)
            else:
                self.driver = webdriver.Chrome(options=options)
            self.driver.maximize_window()
            self.driver.implicitly_wait(5)
        else:
            # 初始化driver后，所有页面需要用这个方法，避免重复构造driver
            self.driver = driver

        # base_url打开页面
        if self.base_url != "":
            self.driver.get(self.base_url)

    def _by(self, string):
        """
        通用封装by
        :param string:
        :return:
        """
        try:
            if string.lower() in ('id',):
                return By.ID
            if string.lower() in ('name',):
                return By.NAME
            if string.lower() in ('css', 'css selector'):
                return By.CSS_SELECTOR
            if string.lower() in ('xpath',):
                return By.XPATH
            if string.lower() in ('linktext', 'link text'):
                return By.LINK_TEXT
            if string.lower() in ('plinktext', 'partial link text'):
                return By.PARTIAL_LINK_TEXT
            if string.lower() in ('tag', 'tag name'):
                return By.TAG_NAME
            if string.lower() in ('class', 'class name'):
                return By.CLASS_NAME
            else:
                self.log().info(f"{string}定位方法不存在！")
                return string
        except Exception as e:
            self.log().error(f"{string}不是字符串！")
            return string

    def find(self, by, locator):
        """
        查找单个元素
        :param by:
        :param locator:
        :return:
        """
        self.log.info(f"操作元素:{by}={locator}")
        try:
            return self.driver.find_element(self._by(by), locator)
        except Exception as e:
            self.log.info(f"{by}:{locator}查找元素失败!")

    def finds(self, by, locator):
        """
        查找元素组
        :param by:
        :param locator:
        :return:
        """
        try:
            return self.driver.find_elements(self._by(by), locator)
        except Exception as e:
            self.log.info(f"{by}={locator}查找元素失败!")
        pass

    def click(self, by, locator):
        try:
            self.find(by, locator).click()
            self.log.info(f"点击元素{by}={locator}完成")
        except Exception as e:
            self.log.info(f"点击元素{by}={locator}失败!")

    def input(self, by, locator, value):
        try:
            self.find(by, locator).send_keys(value)
            self.log.info(f"元素{by}={locator}输入【{value}】完成")
        except Exception as e:
            self.log.info(f"元素{by}={locator}输入【{value}】失败")

    def wait_for_visible(self, *locator, timeout=10):
        """
        等待元素显示，locator:使用简写css或者By.CSS_SELECTOR均可，支持8种定位方式。例如wait_for_visible('css', '.layui-table-body.layui-table-main>table')或wait_for_visible(By.XPATH, '//*[@id="user"]')
        :param locator:使用简写css或者By.CSS_SELECTOR均可，支持8种定位方式
        :param timeout:默认10秒
        :return:
        """
        locator = (self._by(locator[0]), locator[1])
        try:
            element: WebElement = WebDriverWait(self.driver, timeout).until(
                expected_conditions.visibility_of_element_located(locator))
            return element
        except Exception as e:
            capture = self.allure_screenshot(self.capture_path)
            self.log.error(f"{locator}元素超时未显示,截图位置：【{capture}】!")

            return False

    def until_input_success(self, *locator, value, timeout=10):
        """
        等待内容输入成功，默认重试10秒，间隔0.5重试一次
        :param locator:
        :param value:
        :param timeout:
        :return:
        """

        def wait_for_next(x: BasePage):

            try:
                x.input(*locator,value)
                return True
            except Exception as e:
                self.log.error(f"{locator}输入{value}失败!")
                return False

        try:
            WebDriverWait(self, timeout, 0.5).until(wait_for_next)
            return True
        except Exception as e:
            # 循环失败后，截屏并将结果挂接在allure报告中
            capture = self.allure_screenshot(self.capture_path)
            self.log.error(f"{locator}循环输入{value}失败!截图位置：【{capture}】")
            return False

    def until_click_success(self, *locator, timeout=10):
        """
        点击元素，直到成功，默认重试10秒，间隔0.5重试一次
        :param locator:
        :param timeout:
        :return:
        """

        def wait_for_next(x: BasePage):

            try:
                x.click(*locator)
                return True
            except Exception as e1:
                self.log.error(f"点击: {locator}失败!")
                return False

        try:
            WebDriverWait(self, timeout, 0.5).until(wait_for_next)
            return True
        except Exception as e:
            # 循环失败后，截屏并将结果挂接在allure报告中
            capture = self.allure_screenshot(self.capture_path)
            self.log.error(f"循环点击: {locator}失败!截图位置：【{capture}】")
            return False

    def until_switch_frame_success(self, *locator, timeout=10):
        """
        直到iframe元素切换成功，重试10秒，间隔0.5秒一次
        :param locator:
        :param timeout:
        :return:
        """
        # 等待切换frame出现且切换成功
        locator = (self._by(locator[0]), locator[1])
        try:
            WebDriverWait(self.driver, timeout, 0.5).until(
                expected_conditions.frame_to_be_available_and_switch_to_it(locator))
            self.log.info(f"切换frame: {locator}成功!")
        except NoSuchFrameException as e:
            # 切换frame失败，截屏
            capture = self.allure_screenshot(self.capture_path)
            self.log.error(f"切换frame: {locator}失败!截图位置：【{capture}】")
            raise e

    def allure_screenshot(self, capture_path):
        """
        截屏后挂接在allure报告中
        :param capture_path: 截屏存放路径
        :return:None
        """
        # 判断存放截图的目录是否存在，不存在则创建
        if os.path.exists(capture_path) is False:
            # os.makedirs(path) 多层创建目录，os.mkdir(path) 创建目录
            os.makedirs(capture_path)
        time_flag = datetime.datetime.now().strftime('%Y%m%d%H%M%S.%f')
        capture_full_path = f"{capture_path}/{time_flag}.png"
        # 重试超时，且出错后截图
        self.driver.save_screenshot(capture_full_path)
        # 读取图片，附加到allure中，以便在生成的报告中挂上图片
        with open(capture_full_path, "rb") as f:
            content = f.read()
        allure.attach(content, attachment_type=allure.attachment_type.PNG)
        return capture_full_path

    def execute_js(self, script, *args):
        """
        执行js脚本封装，增加日志及捕获异常进行输出
        :param script:
        :param args:
        :return:
        """
        if len(args) == 0:
            try:
                result = self.driver.execute_script(script)
                self.log.info(f'执行js脚本：【{script}】成功。')
                return result
            except Exception:
                self.log.info(f'执行js脚本：【{script}】失败！')
                return ""
        else:
            try:
                result = self.driver.execute_script(script, *args)
                self.log.info(f'执行js脚本：【{script},{args}】成功。')
                return result
            except Exception:
                self.log.info(f'执行js脚本：【{script},{args}】失败！')
                return ""

    def input_start_end_time(self, start_css_locator='StartDefaultLocator', start_time='',
                             end_css_locator='EndDefaultLocator', end_time=''):
        """
        通过js输入时间控件的开始和结束时间
        :param start_css_locator: 开始时间控件的css定位
        :param start_time: 开始日期
        :param end_css_locator: 结束时间控件的css定位
        :param end_time: 结束日期
        :return:
        """

        # 判断是否给出了开始或结束时间的定位css，未给则不执行js
        if start_css_locator != 'StartDefaultLocator':
            # 未显示的元素不执行js
            if self.wait_for_visible('css', start_css_locator) is not False:
                self.execute_js(f'$("{start_css_locator}").text("{start_time}")')
        if end_css_locator != 'EndDefaultLocator':
            # 未显示的元素不执行js
            if self.wait_for_visible('css', end_css_locator) is not False:
                self.execute_js(f'$("{end_css_locator}").text("{end_time}")')
        else:
            self.log.info(
                f"参数输入错误：start_css_locator={start_css_locator}, start_time={start_time},end_css_locator={end_css_locator}, end_time={end_time}")

    def select(self, choice_element_css, option_element_css):
        """
        使用方式：self.select('#select-processStatus', f'#popup-processStatus>li[data-text="{status}"]')
        :param choice_element_css: 下拉框元素的css定位
        :param option_element_css: 点击下拉后，需要选中选项的css定位
        :return:
        """
        try:
            self.click('css', choice_element_css)
            self.click('css', option_element_css)
        except Exception as e:
            # 切换frame失败，截屏
            capture = self.allure_screenshot(self.capture_path)
            self.log.error(f"选择失败: {option_element_css}选择失败!截图位置：【{capture}】")
            raise e

    def quit_browser(self):
        """
        关闭所有浏览器
        :return:
        """
        self.driver.close()
        self.driver.quit()




